#include <sys/epoll.h>
#include "wrap.h"

/**
 * 多路 IO 转接（多路IO服用）：内核监听多个文件描述符的属性（读写缓冲区）变化
 * 如果某个文件描述符的读缓冲区变化了，就可以进行读操作为了。会将这个事件告诉应用层。
 *      select      Windows平台使用select，跨平台
 *      epoll       Linux
 *      poll        很少使用
 * @return
 */
//int main(int argc,char * argv[]) {return 1;}

#define PORT 8080


/**
 * select
 *      优点
 *          跨平台
 *      缺点
 *          文件描述符的限制 1024 由于FD_SETSIZE的限制
 *          只是返回变化的文件描述符的个数，具体的变化需要遍历
 *          每次都需要将需要监听的文件描述符给到内核
 *          大量并发，少量活跃，select效率低
 *      可以使用 dup2 对某个文件描述符进行重定向。然后关闭某个文件描述符
 *
 * poll
 *      优点
 *          相对于select 没有 1024文件描述符限制 ulimit cat /proc/sys/fs/file-max
 *          请求和返回时分离的
 *      缺点 基本和select 一样
 *          每次 都需要将文件描述符拷贝到内核
 *          每次都需要将数组中的元素都遍历一次
 *          大量并发，少量活跃时效率太低
 *
 * epoll
 *      工作原理
 *          1 创建红黑树             epoll_create
 *          2 数据上树 下树 修改节点   epoll_ctl
 *          3 监听                  epoll_wait
 *      优点
 *          没有文件描述符 个数限制
 *          以后每次监听都不要再将监听的描述符拷贝到内核中了
 *          返回的是已经变化的文件描述符，不需要遍历整个 红黑树
 *          大量并发，少量活跃 效率高
 *      缺点
 *
 *      epoll_wait 工作方式
 *          针对读缓冲区（熟悉）
 *              水平触发 LT 只要读缓冲区有数据就会触发（如果不做设定默认水平）
 *              边缘触发 ET 数据来一次，就触发一次
 *          针对写缓冲区（了解）
 *              水平触发 LT 只要可以写就会触发
 *              边缘触发 ET 数据从有到无就会触发
 *
 * @param argc
 * @param argv
 * @return
 */
int main(int argc,char * argv[]) {
    // 创建套接字 绑定
    int socket_fd = tcp4bind(PORT,NULL);
    // 监听
    Listen(socket_fd,128);
    // 创建树
    int epfd = epoll_create(1);
    // socket_fd 上树
    struct epoll_event ev,allevs[1024];
    ev.data.fd = socket_fd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,socket_fd,&ev);
    // epoll监听
    while (1){
        int readyCount = epoll_wait(epfd,allevs,1024,-1);
        if(readyCount < 0){
            perror("epoll_wait");
            break;
        }
        else if(readyCount == 0){
            continue;
        }
        for (int i = 0; i < readyCount; ++i){
            // 如果当前的是 socket_fd           且   当前事件是读事件变化
            if(allevs[i].data.fd == socket_fd && allevs[i].events & EPOLLIN){
                struct sockaddr_in client_addr;
                char ip[16] = "";
                socklen_t len = sizeof (client_addr);
                int client_fd = Accept(socket_fd,(struct sockaddr*)&client_addr,&len);
                printf("client connect ==> ip= %s port= %d\n", inet_ntop(AF_INET,&client_addr.sin_addr,ip,16), ntohs(client_addr.sin_port));
                // client_fd 上树
                ev.data.fd = client_fd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&ev);
            }
            // 此时是 client_fd  且 得是读事件
            else if(allevs[i].events & EPOLLIN){
                char buff[1500] = "";
                int n = read(allevs[i].data.fd,buff, sizeof(buff));
                // 出错
                if(n < 0){
                    perror("read");
                    close(allevs[i].data.fd);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,allevs[i].data.fd,&allevs[i]);
                }
                // 客户端断开链接
                else if(n == 0){
                    close(allevs[i].data.fd);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,allevs[i].data.fd,&allevs[i]);
                    printf("exit success! \n");
                }
                // 正常
                else{
                    printf("%s",buff);
                    write(allevs[i].data.fd,buff,n);
                }
            }
        }

    }

    return 1;
}

